package com.zc.im.seqsvr.config;

import com.zc.im.common.r2dbc.domain.AbstractBase;
import com.zc.im.common.r2dbc.event.callback.BaseBeforeSaveCallback;
import com.zc.im.seqsvr.converter.BaseEnumToNumberConverter;
import com.zc.im.seqsvr.converter.IntegerToBaseEnumConverterFactory;
import com.zc.im.seqsvr.domain.Test;
import io.r2dbc.pool.ConnectionPool;
import io.r2dbc.pool.ConnectionPoolConfiguration;
import io.r2dbc.spi.ConnectionFactory;
import io.r2dbc.spi.ConnectionFactoryOptions;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryOptionsBuilderCustomizer;
import org.springframework.boot.autoconfigure.r2dbc.R2dbcProperties;
import org.springframework.boot.context.properties.PropertyMapper;
import org.springframework.boot.r2dbc.EmbeddedDatabaseConnection;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.convert.converter.Converter;
import org.springframework.core.io.ResourceLoader;
import org.springframework.data.convert.CustomConversions;
import org.springframework.data.r2dbc.config.AbstractR2dbcConfiguration;
import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback;
import org.springframework.util.ClassUtils;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/**
 * Project：im
 * Date：2022/4/13
 * Time：19:22
 * Description：R2DBC配置类
 *
 * @author Challen.Zhang
 * @version 1.0
 */
@Configuration
public class R2DBCConfig extends AbstractR2dbcConfiguration {
    //@Bean
    //public R2dbcEntityOperations mysqlR2dbcEntityOperations(@Autowired DatabaseClient databaseClient) {
    //
    //    DefaultReactiveDataAccessStrategy strategy = new DefaultReactiveDataAccessStrategy(MySqlDialect.INSTANCE);
    //
    //    return new R2dbcEntityTemplate(databaseClient, strategy);
    //}

    @Autowired
    R2dbcProperties properties;
    @Autowired
    ResourceLoader resourceLoader;
    @Autowired
    ObjectProvider<ConnectionFactoryOptionsBuilderCustomizer> customizers;

    @Bean
    public BeforeSaveCallback<AbstractBase> beforeSaveCallback() {
        return new BaseBeforeSaveCallback();
    }

    @Override
    protected List<Object> getCustomConverters() {
        List<Object> list = new ArrayList<>();
        list.add(new BaseEnumToNumberConverter());
        list.add(new IntegerToBaseEnumConverterFactory());
        return list;
    }


    @Override
    public ConnectionFactory connectionFactory() {
        ConnectionFactory connectionFactory = createConnectionFactory(properties,
                resourceLoader.getClassLoader(), customizers.orderedStream().collect(Collectors.toList()));
        R2dbcProperties.Pool pool = properties.getPool();
        PropertyMapper map = PropertyMapper.get().alwaysApplyingWhenNonNull();
        ConnectionPoolConfiguration.Builder builder = ConnectionPoolConfiguration.builder(connectionFactory);
        map.from(pool.getMaxIdleTime()).to(builder::maxIdleTime);
        map.from(pool.getMaxLifeTime()).to(builder::maxLifeTime);
        map.from(pool.getMaxAcquireTime()).to(builder::maxAcquireTime);
        map.from(pool.getMaxCreateConnectionTime()).to(builder::maxCreateConnectionTime);
        map.from(pool.getInitialSize()).to(builder::initialSize);
        map.from(pool.getMaxSize()).to(builder::maxSize);
        map.from(pool.getValidationQuery()).whenHasText().to(builder::validationQuery);
        map.from(pool.getValidationDepth()).to(builder::validationDepth);
        return new ConnectionPool(builder.build());
    }


    protected static ConnectionFactory createConnectionFactory(R2dbcProperties properties, ClassLoader classLoader,
                                                               List<ConnectionFactoryOptionsBuilderCustomizer> optionsCustomizers) {
        try {
            Class<?> aClass = Class.forName("org.springframework.boot.autoconfigure.r2dbc.ConnectionFactoryOptionsInitializer");
            Object o = aClass.newInstance();
            Method initialize = aClass.getDeclaredMethod("initialize", R2dbcProperties.class, Supplier.class);
            Object invoke = initialize.invoke(o, properties, new Supplier<EmbeddedDatabaseConnection>() {
                @Override
                public EmbeddedDatabaseConnection get() {
                    return EmbeddedDatabaseConnection.get(classLoader);
                }
            });
            return org.springframework.boot.r2dbc.ConnectionFactoryBuilder
                    .withOptions((ConnectionFactoryOptions.Builder) invoke)
                    //new ConnectionFactoryOptionsInitializer().initialize(properties,
                    //() -> EmbeddedDatabaseConnection.get(classLoader)))
                    .configure((options) -> {
                        for (ConnectionFactoryOptionsBuilderCustomizer optionsCustomizer : optionsCustomizers) {
                            optionsCustomizer.customize(options);
                        }
                    })
                    .build();
        } catch (ClassNotFoundException | IllegalArgumentException | InvocationTargetException | InstantiationException | NoSuchMethodException | SecurityException | IllegalAccessException e) {
            throw new RuntimeException();
        } catch (IllegalStateException ex) {
            String message = ex.getMessage();
            if (message != null && message.contains("driver=pool")
                    && !ClassUtils.isPresent("io.r2dbc.pool.ConnectionPool", classLoader)) {
                throw new RuntimeException();
            }
            throw ex;
        }
    }

}
